//
//  XMFTPConnection.m
//  XMFtpServer
//
//  Created by chi on 16/3/28.
//  Copyright © 2016年 chi. All rights reserved.
//

#import "XMFTPConnection.h"

#import "XMFTPServer.h"


@implementation XMFTPConnection

@synthesize transferMode;
@synthesize currentFile;
@synthesize currentDir;
@synthesize rnfrFilename;

// ----------------------------------------------------------------------------------------------------------
-(id)initWithAsyncSocket:(AsyncSocket*)newSocket forServer:(XMFTPServer*)myServer
// ----------------------------------------------------------------------------------------------------------
{
    self = [super init ];
    if (self)
    {
        connectionSocket = newSocket;
        server = myServer;
        [ connectionSocket setDelegate:self ];
        [ connectionSocket writeData:DATASTR(@"220 iosFtp server ready.\r\n") withTimeout:-1 tag:0 ];					// send out the welcome message to the client
        [ connectionSocket readDataToData:[AsyncSocket CRLFData] withTimeout:READ_TIMEOUT tag:FTP_CLIENT_REQUEST ];		// start listening for commands on this connection to client
        dataListeningSocket = nil;
        dataPort=2001;
        transferMode = pasvftp;
        queuedData = [[ NSMutableArray alloc ] init ];								// A buffer for sending data when the connection isn't quite up yet
        self.currentDir = [ server.baseDir copy];												// the working directory for this connection, Starts in the directory the server is set to.  set chroot=true in server code to sandbox in
        currentFile = nil;
        currentFileHandle = nil;													// not saving to a file yet
        rnfrFilename = nil;
        
        currentUser = nil;
        if (g_XMFTP_LogEnabled) {
            XMFTPLog(@"FC: Current Directory starting at %@",currentDir );
        }
    }
    return self;
}
// ----------------------------------------------------------------------------------------------------------
-(void)dealloc
// ----------------------------------------------------------------------------------------------------------
{
    if(connectionSocket)
    {
        [connectionSocket setDelegate:nil];
        [connectionSocket disconnect];
    }
    
    if(dataListeningSocket){														// 12/2/10 - think this code  is now redundant, dataListeningSocket, does it do anything anymore...?
        [dataListeningSocket setDelegate:nil];
        [dataListeningSocket disconnect];
        
    }
    if(dataSocket)
    {
        [dataSocket setDelegate:nil];
        [dataSocket disconnect];
        
    }
}
#pragma mark STATE
// ----------------------------------------------------------------------------------------------------------
//-(void)setTransferMode:(int)mode
// ----------------------------------------------------------------------------------------------------------
//{
//	transferMode = mode;
//}

// ----------------------------------------------------------------------------------------------------------
-(NSString*)connectionAddress
// ----------------------------------------------------------------------------------------------------------
{
    return [connectionSocket connectedHost];
    
}


// ==========================================================================================================
#pragma mark CHOOSE DATA SOCKET
// ==========================================================================================================



// ----------------------------------------------------------------------------------------------------------
-(BOOL)openDataSocket:(int)portNumber
// ----------------------------------------------------------------------------------------------------------
{
    NSString *responseString;
    NSError *error = nil;
    
    if (dataSocket)																			// Code changed 2010-02-12 - As per Jon's fix. stops memory leak on socket and connection
    {
        dataSocket = nil;
    }
    dataSocket = [ [ AsyncSocket alloc ] initWithDelegate:self ];                           // 	create our socket for Listening or Direct Connection
    if (dataConnection)
    {
        dataConnection = nil;
    }
    
    
    
    switch (transferMode) {
        case portftp:
            dataPort = portNumber;
            responseString = [ NSString stringWithFormat:@"200 PORT command successful."];
            [ dataSocket connectToHost:[self connectionAddress] onPort:portNumber error:&error ];
            //			sleep(1);
            dataConnection = [[ XMFTPDataConnection alloc ] initWithAsyncSocket:dataSocket forConnection:self withQueuedData:queuedData ];
            
            break;
            
        case lprtftp:					// FIXME wrong return message
            dataPort = portNumber;
            responseString = [ NSString stringWithFormat:@"228 Entering Long Passive Mode 	(af, hal, h1, h2, h3,..., pal, p1, p2...)"];//, dataPort >>8, dataPort & 0xff];
            [ dataSocket connectToHost:[self connectionAddress] onPort:portNumber error:&error ];
            dataConnection = [[ XMFTPDataConnection alloc ] initWithAsyncSocket:dataSocket forConnection:self withQueuedData:queuedData ];
            break;
            
        case eprtftp:
            dataPort = portNumber;
            responseString = @"200 EPRT command successful.";
            [ dataSocket connectToHost:[self connectionAddress] onPort:portNumber error:&error ];
            dataConnection = [[ XMFTPDataConnection alloc ] initWithAsyncSocket:dataSocket forConnection:self withQueuedData:queuedData ];
            break;
            
        case pasvftp:
        {
            dataPort = [ self choosePasvDataPort ];
            NSString *address = [ [connectionSocket localHost ] stringByReplacingOccurrencesOfString:@"." withString:@"," ]; // autoreleased
            responseString = [ NSString stringWithFormat:@"227 Entering Passive Mode (%@,%d,%d)",address, dataPort >>8, dataPort & 0xff];
            [ dataSocket acceptOnPort: dataPort error:&error  ];
            dataConnection = nil;  // will pickup from the listening socket
            
        }
            
            break;
        case epsvftp:
        {
            dataPort = [ self choosePasvDataPort ];
            responseString = [ NSString stringWithFormat:@"229 Entering Extended Passive Mode (|||%d|)", dataPort ];
            [ dataSocket acceptOnPort: dataPort error:&error  ];
            
            dataConnection = nil;  // will pickup from the listening socket
        }
            break;
            
            
        default:
            break;
    }
    
    if (g_XMFTP_LogEnabled) {
        XMFTPLog( @"-- %@", [ error localizedDescription ] );
    }
    
    [ self sendMessage:responseString ];
    
    return YES;
}


// ----------------------------------------------------------------------------------------------------------
-(int)choosePasvDataPort
// ----------------------------------------------------------------------------------------------------------
{
    struct timeval tv;
    unsigned short int seed[3];
    
    gettimeofday(&tv, NULL);
    seed[0] = (tv.tv_sec >> 16) & 0xFFFF;
    seed[1] = tv.tv_sec & 0xFFFF;
    seed[2] = tv.tv_usec & 0xFFFF;
    seed48(seed);
    
    int portNumber;
    portNumber = (lrand48() % 64512) + 1024;
    //	NSLog(@"New Port number is %i", portNumber );
    
    return portNumber;																// FIXME - presetting to 2001 for the moment
    
    //return 2001;
    
}



// ==========================================================================================================
#pragma mark ASYNCSOCKET DATACONNECTION
// ==========================================================================================================


// ----------------------------------------------------------------------------------------------------------
-(BOOL)onSocketWillConnect:(AsyncSocket *)sock
// ----------------------------------------------------------------------------------------------------------
{
    
    if (g_XMFTP_LogEnabled) {
        XMFTPLog(@"FC:onSocketWillConnect");
    }
    [ sock readDataWithTimeout:READ_TIMEOUT tag:0 ];
    
    return YES;
}

// ----------------------------------------------------------------------------------------------------------
-(void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket		// should be for a data connection on 2001
// ----------------------------------------------------------------------------------------------------------
{
    // opened socket for passive data  - new socket connected to this which is the passive connection
    
    if (g_XMFTP_LogEnabled) {
        XMFTPLog(@"FC:New Connection -- should be for the data port");
    }
    
    dataConnection = [[ XMFTPDataConnection alloc ] initWithAsyncSocket:newSocket forConnection:self  withQueuedData:queuedData];
}



// ==========================================================================================================
#pragma mark ASYNCSOCKET FTPCLIENT CONNECTION
// ==========================================================================================================

// ----------------------------------------------------------------------------------------------------------
-(void)onSocket:(AsyncSocket*)sock didReadData:(NSData*)data withTag:(long)tag			// DATA READ
// ----------------------------------------------------------------------------------------------------------
{
    if (g_XMFTP_LogEnabled) {
        XMFTPLog(@"FC:didReadData");
    }

    [ connectionSocket readDataToData:[AsyncSocket CRLFData] withTimeout:READ_TIMEOUT tag:FTP_CLIENT_REQUEST ];		// start reading again
    
    [ self processDataRead:data ];
    
}


// ----------------------------------------------------------------------------------------------------------
-(void)onSocket:(AsyncSocket*)sock didWriteDataWithTag:(long)tag						// DATA WRITTEN
// ----------------------------------------------------------------------------------------------------------
{
    [ connectionSocket readDataToData:[AsyncSocket CRLFData] withTimeout:READ_TIMEOUT tag:FTP_CLIENT_REQUEST ];	// start reading again
    //	NSLog(@"FC:didWriteData");
    
}

// ----------------------------------------------------------------------------------------------------------
-(void)sendMessage:(NSString*)ftpMessage				// REDUNDANT really - FIXME
// ----------------------------------------------------------------------------------------------------------
{
    
    if (g_XMFTP_LogEnabled) {
        XMFTPLog(@">%@",ftpMessage );
    }
    
    NSMutableData *dataString = [[ ftpMessage dataUsingEncoding:NSUTF8StringEncoding ] mutableCopy];				// Autoreleased
    [ dataString appendData:[AsyncSocket CRLFData] ];
    
    [ connectionSocket writeData:dataString withTimeout:READ_TIMEOUT tag:FTP_CLIENT_REQUEST ];
    [ connectionSocket readDataToData:[AsyncSocket CRLFData] withTimeout:READ_TIMEOUT tag:FTP_CLIENT_REQUEST ];	// start reading again
    //	[ connectionSocket readDataWithTimeout:READ_TIMEOUT tag:FTP_CLIENT_REQUEST ];
}
// ----------------------------------------------------------------------------------------------------------
-(void)sendDataString:(NSString*)dataString
// ----------------------------------------------------------------------------------------------------------
{
    NSMutableString *message = [[NSMutableString alloc] initWithString:dataString];
    CFStringNormalize((CFMutableStringRef)message, kCFStringNormalizationFormC);
    NSMutableData *data = [[ message dataUsingEncoding:server.clientEncoding ] mutableCopy];				// Autoreleased
    
    if (dataConnection )
    {
        if (g_XMFTP_LogEnabled) {
             XMFTPLog(@"FC:sendData");
        }
        [ dataConnection writeData:data ];
        
    }
    else
    {
        [ queuedData addObject:data ];
    }
    
}
// ----------------------------------------------------------------------------------------------------------
-(void)sendData:(NSMutableData*)data
// ----------------------------------------------------------------------------------------------------------
{
    if (dataConnection )
    {
        if (g_XMFTP_LogEnabled) {
            XMFTPLog(@"FC:sendData");
        }
        
        [ dataConnection writeData:data ];
    }
    else
    {
        [ queuedData addObject:data ];
    }
    
}
// ----------------------------------------------------------------------------------------------------------
-(void)didReceiveDataWritten			// notification from FtpDataConnection that the dataWasWritten
// ----------------------------------------------------------------------------------------------------------
{
    
    if (g_XMFTP_LogEnabled) {
        XMFTPLog(@"SENDING COMPLETED");
    }
    
    [ self sendMessage:@"226 Transfer complete." ]; // send completed message to client
    [ dataConnection closeConnection ];
}

// ----------------------------------------------------------------------------------------------------------
-(void)didReceiveDataRead			// notification from FtpDataConnection that the dataWasWritten
// ----------------------------------------------------------------------------------------------------------
{
    
    //NSLog(@"FC:didReceiveDataRead");
    
    // must have sent a file
    
    // Should start writing file out if not written yet : FIXNOW
    
    if ( currentFileHandle != nil )
    {
        // Append data on
        //NSLog(@"FC:Writing File to %@", currentFile );
        [ currentFileHandle writeData:dataConnection.receivedData ];
        
    }
    else
    {
        if (g_XMFTP_LogEnabled) {
            XMFTPLog(@"Couldn't write data");
        }
    }
    
    
    
}

// ----------------------------------------------------------------------------------------------------------
-(void)didFinishReading							// Called at the end of a data connection from the client we presume
// ----------------------------------------------------------------------------------------------------------
{
    if (currentFile)
    {
        if (g_XMFTP_LogEnabled) {
            XMFTPLog(@"Closing File Handle");
        }
        currentFile = nil;
    }
    else
    {
        if (g_XMFTP_LogEnabled) {
            XMFTPLog(@"FC:Data Sent but not sure where its for ");
        }
    }
    
    [ self sendMessage:@"226 Transfer complete." ]; // send completed message to client
    
    //	[ dataConnection closeConnection ];									// It must be closed, it dropped us
    
    if ( currentFileHandle != nil )
    {
        if (g_XMFTP_LogEnabled) {
            XMFTPLog(@"Closing File Handle");
        }
        
        [ currentFileHandle closeFile ];								// Close the file handle
        currentFileHandle = nil;
        [ server didReceiveFileListChanged];
    }
    
    dataConnection.connectionState = clientQuiet;
    
}


// ==========================================================================================================
#pragma mark PROCESS
// =========================================================================================================

// ----------------------------------------------------------------------------------------------------------
-(void)processDataRead:(NSData*)data			// convert to commands as Client Connection
// ----------------------------------------------------------------------------------------------------------
{
    NSData *strData = [data subdataWithRange:NSMakeRange(0, [data length] - 2)];				// remove last 2 chars
    NSString *crlfmessage = [[NSString alloc] initWithData:strData encoding:server.clientEncoding];
    NSString *message;
    
    //	message = [ crlfmessage stringByReplacingOccurrencesOfString:@"\r\n" withString:@""];		// gets autoreleased
    
    message = [ crlfmessage stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet ]];
    
    if (g_XMFTP_LogEnabled) {
        XMFTPLog(@"<%@",message );
    }

    msgComponents = [message componentsSeparatedByString:@" "];										// change this to use spaces - for the FTP protocol
    
    [ self processCommand ];
    
    [connectionSocket readDataToData:[AsyncSocket CRLFData] withTimeout:-1 tag:0 ];					// force to readdata CHECK
}

// ----------------------------------------------------------------------------------------------------------
-(void)processCommand // assumes data has been place in Array msgComponents
// ----------------------------------------------------------------------------------------------------------
{
    NSString *commandString =  [ msgComponents objectAtIndex:0];
    
    if ([ commandString length ] > 0)																// If there is a command here
    {
        // Search through dictionary for correct matching command and method that it calls
        
        NSString *commandSelector = [ [[server commands] objectForKey:[commandString lowercaseString] ] stringByAppendingString:@"arguments:"];
        
        if ( commandSelector )																		// If we have a matching command
        {
            
            SEL action = NSSelectorFromString(commandSelector);										// Turn into a method
            
            if ( [ self respondsToSelector:action ])												// If we respond to this method
            {
                // DO COMMAND
                [self performSelector:action withObject:self withObject:msgComponents ];			// Preform method with arguments
            }
            else
            {			// UNKNOWN COMMAND
                NSString *outputString =[ NSString stringWithFormat:@"500 '%@': command not understood.", commandString ];
                [ self sendMessage:outputString ];
                if (g_XMFTP_LogEnabled) {
                    XMFTPLog(@"DONT UNDERSTAND");
                }
                
            }
        }
        else			// UNKNOWN COMMAND
        {
            NSString *outputString =[ NSString stringWithFormat:@"500 '%@': command not understood.", commandString ];
            [ self sendMessage:outputString ];
        }
    }
    else
    {
        // Write out an error msg
    }
    
}

// ==========================================================================================================
#pragma mark COMMANDS
// ==========================================================================================================

// ----------------------------------------------------------------------------------------------------------
-(void)doQuit:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
    if (g_XMFTP_LogEnabled) {
        XMFTPLog(@"Quit : %@",arguments);
    }
    
    [ self sendMessage:@"221- Data traffic for this session was 0 bytes in 0 files"];
    
    [self sendMessage:@"221 Thank you for using the FTP service on localhost." ];
    
    if(connectionSocket)
    {
        [connectionSocket disconnectAfterWriting ];				// Will this close the socket ?
        //	[connectionSocket disconnect];
    }
    
    [ server closeConnection:self ];			// Tell the server to close us down, remove us from the list of connections
    
    
    // FIXME - delete the dataconnection if its open
    
    
}
// ----------------------------------------------------------------------------------------------------------
-(void)doUser:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
    // send out confirmation message -- 331 password required for
    if ( currentUser != nil )
    currentUser =  [[ arguments objectAtIndex:1 ] copy];
    NSString *outputString = [ NSString stringWithFormat:@"331 Password required for %@", currentUser ];
    [ sender sendMessage:outputString];
}
// ----------------------------------------------------------------------------------------------------------
-(void)doPass:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
    //	NSString *pass = [ arguments objectAtIndex:1 ];
    NSString *outputString = [ NSString stringWithFormat:@"230 User %@ logged in.", currentUser ];
    [ sender sendMessage:outputString];
}
// ----------------------------------------------------------------------------------------------------------
-(void)doStat:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
    // Send out stat message
    [ sender sendMessage:@"211-localhost FTP server status:"];
    // FIXME - add in the stats
    [ sender sendMessage:@"211 End of Status"];
}
// ----------------------------------------------------------------------------------------------------------
-(void)doFeat:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
    [ sender sendMessage:@"211-Features supported"];
    
    // If encoding is UTF8, notify the client
    if (server.clientEncoding == NSUTF8StringEncoding)
        [ sender sendMessage:@" UTF8" ];
    
    [ sender sendMessage:@"211 End"];
}




// ----------------------------------------------------------------------------------------------------------
-(void)doList:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
    // Get the name of any additional directory that we are asked to list,  if its empty we use the current directory
    
    NSString *lsDir = [ self fileNameFromArgs:arguments] ;				// autoreleased,  get the directory that we're being asked for
    NSString *listText;
    
    
    if ([lsDir length]<1) {
        lsDir = currentDir;
    }
    else {
        lsDir = [self rootedPath:lsDir ];
    }
    
    
    if (g_XMFTP_LogEnabled) {
        XMFTPLog( @"doList currentDir(%@) changeRoot%d", lsDir, server.changeRoot );
        XMFTPLog(@"Will list %@ ",lsDir);
    }
    listText = [ [ server createList:lsDir] copy ];						// Can list directory so do it
    //	if ([self canChangeDirectoryTo:lsDir]) {
    //		listText = [ [ server createList:lsDir] retain ];						// Can list directory so do it
    //	}
    //	else {
    //		listText = @"";															// return nothing as not in chroot
    //	}
    
    if (g_XMFTP_LogEnabled) {
        XMFTPLog( @"doList sending this. %@", listText );
    }

    
    [ sender sendMessage:@"150 Opening ASCII mode data connection for '/bin/ls'."];
    
    [ sender sendDataString:listText  ];
}
// ----------------------------------------------------------------------------------------------------------
-(void)doPwd:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
    
    if (g_XMFTP_LogEnabled) {
        XMFTPLog(@"Will PWD %@ ",[self visibleCurrentDir]);
    }

    
    // CHECKME - changed to show basedir - currentdir
    
    
    NSString *cmdString = [ NSString stringWithFormat:@"257 \"%@\" is the current directory.", [self visibleCurrentDir] ]; // autoreleased
    [ sender sendMessage:cmdString ];			// FIXME - seems to be buggy on ftp command line client
    
}
// ----------------------------------------------------------------------------------------------------------
-(void)doNoop:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
    [sender sendMessage:@"200 NOOP command successful." ];
    
}
// ----------------------------------------------------------------------------------------------------------
-(void)doSyst:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
    [ sender sendMessage:@"215 UNIX Type: L8 Version: iosFtp 20080912" ];
}
// ----------------------------------------------------------------------------------------------------------
-(void)doLprt:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
    // LPRT,"6,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,231,92"
    NSString *socketDesc = [ arguments objectAtIndex:1 ] ;
    NSArray *socketAddr = [ socketDesc componentsSeparatedByString:@"," ];
    
    int hb = [[socketAddr objectAtIndex:19] intValue ];
    int lb = [[socketAddr objectAtIndex:20] intValue ];
    
    if (g_XMFTP_LogEnabled) {
        XMFTPLog(@"%d %d %d",hb <<8, hb,lb );
    }

    int clientPort = (hb <<8 ) + lb;
    
    [sender setTransferMode:lprtftp];
    
    [ sender openDataSocket:clientPort ];
}
// ----------------------------------------------------------------------------------------------------------
-(void)doEprt:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
    // EPRT |2|1080::8:800:200C:417A|5282|
    
    NSString *socketDesc = [ arguments objectAtIndex:1 ] ;
    NSArray *socketAddr = [ socketDesc componentsSeparatedByString:@"|" ];
    
    NSString *item;
    for (item in socketAddr) {
        if (g_XMFTP_LogEnabled) {
            XMFTPLog(@"%@", item);
        }
    }
    int clientPort = [[ socketAddr objectAtIndex:3 ] intValue ];
    
    if (g_XMFTP_LogEnabled) {
        XMFTPLog(@"Got Send Port %d", clientPort );
    }
    
    [sender setTransferMode:eprtftp];
    //	[ sender initDataSocket:clientPort ];
    [ sender openDataSocket:clientPort ];
}


// ----------------------------------------------------------------------------------------------------------
-(void)doPasv:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
    [sender setTransferMode:pasvftp];
    //	[ sender initDataSocket:0 ];
    [ sender openDataSocket:0 ];
    
}
// ----------------------------------------------------------------------------------------------------------
-(void)doEpsv:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
    // FIXME - open a port random high address
    [sender setTransferMode:epsvftp];
    //	[ sender initDataSocket:0 ];
    [ sender openDataSocket:0 ];
}
// ----------------------------------------------------------------------------------------------------------
-(void)doPort:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
    int hb, lb;
    
    //  PORT 127,0,0,1,197,251  looks like this
    // get 2nd argument and split up by , and then take the last 2 bits
    
    NSString *socketDesc = [ arguments objectAtIndex:1 ] ;
    NSArray *socketAddr = [ socketDesc componentsSeparatedByString:@"," ];
    
    hb = [[socketAddr objectAtIndex:4] intValue ];
    lb = [[socketAddr objectAtIndex:5] intValue ];
    
    
    int clientPort = (hb <<8 ) + lb;
    
    
    [sender setTransferMode:portftp];
    
    [ sender openDataSocket:clientPort ];
    
}

// ----------------------------------------------------------------------------------------------------------
-(void)doOpts:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
    NSString *cmd = [ arguments objectAtIndex:1 ];
    NSString *cmdstr = [ NSString stringWithFormat:@"502 Unknown command '%@'",cmd ];
    [ sender sendMessage:cmdstr ];
    //	502 Unknown command 'sj'
}
// ----------------------------------------------------------------------------------------------------------
-(void)doType:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
    //		200 Type set to A.
    
    // FIXME - change the data output to the matching type -- we dont do anything with this yet,, there are many types apart from this one.
    
    NSString *cmd =  [ arguments objectAtIndex:1 ];
    
    //if ( [ [cmd lowercaseString] isEqualToString:@"i" ])
    //	{
    NSString *cmdstr = [ NSString stringWithFormat:@"200 Type set to  %@.",cmd ];
    [ sender sendMessage:cmdstr ];
    //	}
    //	else
    //	{
    //		NSString *cmdstr = [ NSString stringWithFormat:@"500 'type %@': command not understood.",cmd ];
    //		[ sender sendMessage:cmdstr ];
    //	}
}
// ----------------------------------------------------------------------------------------------------------
-(void)doCwd:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
    //	250
    //	500, 501, 502, 421, 530, 550
    //	250 CWD command successful.
    
    if (g_XMFTP_LogEnabled) {
        XMFTPLog(@"DoCwd arguments is %@",arguments);
    }
    
    NSString *cmdstr;
    NSString *cwdDir = [ self fileNameFromArgs:arguments] ;				// autoreleased,  get the directory that we're being asked for
    
    if ( [ self changedCurrentDirectoryTo:cwdDir ] )											// tries to change to that directory, checks in bounds and viable
    {
        cmdstr = [ NSString stringWithFormat:@"250 OK. Current directory is %@", [self visibleCurrentDir]];		// currentDir is now in the new place
        cmdstr = @"250 CWD command successful.";
    }
    else
    {
        cmdstr = @"550 CWD failed.";
    }
    
    [ sender sendMessage:cmdstr];
    
    if (g_XMFTP_LogEnabled) {
        XMFTPLog(@"currentDir is now %@",currentDir );
    }
    
}
// ----------------------------------------------------------------------------------------------------------
-(void)doNlst:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
    [self doList:sender arguments:arguments ];
}
// ----------------------------------------------------------------------------------------------------------
-(void)doStor:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
    // eg STOR my filename here.mp3        STOR=0 my=1 filename=2 etc
    
    NSFileManager	*fs = [NSFileManager defaultManager];
    NSString		*filename = [ self fileNameFromArgs:arguments];		// autoreleased
    NSString		*cmdstr;
    
    self.currentFile = [self makeFilePathFrom:filename];														// makes a filepath, using absolute or relative filename
    
    // Check this falls within the area of filesystem we are allowed to write to
    if ( [self validNewFilePath:self.currentFile] )																// FIXME - finish function for test
    {
        // CREATE and then OPEN NEW FILE FOR WRITING
        if ([fs createFileAtPath:self.currentFile contents:nil attributes:nil]==YES)
        {
            currentFileHandle = [ NSFileHandle fileHandleForWritingAtPath:currentFile];				// Open the file handle to write to
            cmdstr   = [ NSString stringWithFormat:@"150 Opening BINARY mode data connection for '%@'.",filename ];	// autoreleased
        }
        else
        {
            // couldn't make file, send out the error
            cmdstr = [ NSString stringWithFormat:@"553 %@: Permission denied.", filename ];
            
        }
    }
    else
    {
        // couldn't make file as out of root area
        cmdstr = [ NSString stringWithFormat:@"553 %@: Permission denied.", filename ];
    }
    
    if (g_XMFTP_LogEnabled) {
        XMFTPLog(@"FC:doStor  %@", currentFile );
    }
    
    
    [sender sendMessage:cmdstr];
    
    // CHECKME - data connection should have been brought up by client?
    if (dataConnection )																			// if not we're in trouble. mind u currentFile is doing similar job.
    {
        if (g_XMFTP_LogEnabled) {
            XMFTPLog(@"FC:setting connection state to clientSending");
        }

        dataConnection.connectionState = clientSending;
    }
    else
    {
        if (g_XMFTP_LogEnabled) {
            XMFTPLog(@"FC:Erorr  Cant set connection state to Client Sending : no Connection yet ");
        }
    }
    
}

// ----------------------------------------------------------------------------------------------------------
-(void)doRetr:(id)sender arguments:(NSArray*)arguments			// DOWNLOAD to CLIENT
// ----------------------------------------------------------------------------------------------------------
{
    BOOL isDir;
    NSString *cmdstr;
    
    NSString *filename = [self fileNameFromArgs:arguments];		// autoreleased
    NSString *filePath = [self makeFilePathFrom:filename];			// turns relative or absolute path into format we need
    
    if (g_XMFTP_LogEnabled) {
        XMFTPLog(@"FC:doRetr: %@", filePath );
    }
    
    if ( [self accessibleFilePath:filePath ] )					// if this filepath is in rooted area, and is a real file
    {
        if ( [ [ NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory: &isDir ])			// FIXME - fold into previous line
        {
            if ( isDir ){																				// reject if its a directory request
                [ sender sendMessage: [NSString stringWithFormat:@"550 %@: Not a plain file.",filename]];
            }
            else																						// SEND FILE
            {	// FIXME URGENT - need to stop loading whole file into memory to send
                NSMutableData   *fileData = [ NSMutableData dataWithContentsOfFile:filePath ];										// FIXME - open in bits ? seems risky opening file in one piece
                cmdstr = [ NSString stringWithFormat:@"150 Opening BINARY mode data connection for '%@'.",filename ];
                [sender sendMessage:cmdstr];
                
                [ sender sendData:fileData  ];																								// Send file
            }
        }
    }
    else			// doesn't exist or not in basedir sandbox
    {
        cmdstr = [ NSString stringWithFormat:@"50 %@ No such file or directory.",filename ];
        if (g_XMFTP_LogEnabled) {
            XMFTPLog(@"FC:doRetr: file %@ doesnt' exist ", filePath);
        }
        
        [sender sendMessage:cmdstr];
    }
    
    
}

// ----------------------------------------------------------------------------------------------------------
-(void)doDele:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
    NSString *cmdStr;
    NSError  *error;
    NSString *filename =[self fileNameFromArgs:arguments];		// autoreleased
    NSString *filePath = [self makeFilePathFrom:filename];
    
    if (g_XMFTP_LogEnabled) {
        XMFTPLog(@"filename is %@",filename);
    }
    
    // attempt to delete the file
    
    if ( [self accessibleFilePath:filePath ])						// exists and can access
    {
        if ([[ NSFileManager defaultManager ] removeItemAtPath:filePath error:&error ])
        {
            cmdStr = [ NSString stringWithFormat:@"250 DELE command successful.",filename ];
            [ server didReceiveFileListChanged];				// Addition by Brendan copied in by Rich 16/12/08
        }
        else
        {
            cmdStr = [ NSString stringWithFormat:@"550 DELE command unsuccessful.",filename ];					// FIXME put correct error code in
        }
    }
    else
    {
        cmdStr = [ NSString stringWithFormat:@"550 %@ No such file or directory.", filename];
    }
    
    [ sender sendMessage:cmdStr	];
    
    
}
// ----------------------------------------------------------------------------------------------------------
-(void)doMlst:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
    NSString *filename = [self fileNameFromArgs: arguments];
    NSString *cmdstr = [ NSString stringWithFormat:@"150 Opening BINARY mode data connection for '%@'.",filename ];
    // tell connection to expect a file
    
    [sender sendMessage:cmdstr];										// FIXME - this doesn't do anything beyond respond with first message
    /*  typiccal output to generate
     250: MLST 2012.pdf
     Type=file;Size=2420017;Modify=20080808074805;Perm=adfrw;Unique=AgAADpIHZwA; /Users/monsta/Documents/2012.pdf
     End
     */
}
// ----------------------------------------------------------------------------------------------------------
-(void)doSize:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
    NSString *cmdStr;
    NSString *filename = [self fileNameFromArgs: arguments];									// Autoreleased
    NSString *filePath = [self makeFilePathFrom:filename];										// Autoreleased
    
    if ([self accessibleFilePath:filePath])														// Can we reach that file ?
    {
        if ([self fileSize:filePath] < 10240)													// If small enough for old style size command
        {
            cmdStr = [ NSString stringWithFormat:@"213 %qu",[self fileSize:filePath] ];			// report size
        }
        else
        {
            cmdStr = [ NSString stringWithFormat:@"550 %@ file too large for SIZE.",filename ];
        }
    }
    else
    {
        cmdStr = [ NSString stringWithFormat:@"550 %@ No such file or directory.",filename ];	// report file not found
    }
    
    // tell connection to expect a file
    
    [sender sendMessage:cmdStr];
    
}


// ----------------------------------------------------------------------------------------------------------
-(void)doMkdir:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
    if (g_XMFTP_LogEnabled) {
        XMFTPLog(@"current dir is %@",currentDir);
    }
    
    NSString *cmdStr;
    NSString		*p  = [self makeFilePathFrom:[self fileNameFromArgs:arguments]];
    NSFileManager	*fs = [NSFileManager defaultManager];
    
    if ( [self validNewFilePath:p ])										// FIXME - make sure function works. see function
    {
        if( [fs fileExistsAtPath:p isDirectory:nil] )
        {
            cmdStr = [ NSString stringWithFormat:@"Error %@ exists",[self fileNameFromArgs:arguments] ];
        }
        else
        {
            // FIXME  - check that its ok to make a directory in this position
            
            [fs createDirectoryAtPath:p withIntermediateDirectories:YES attributes:nil error:nil];
            cmdStr = [ NSString stringWithFormat:@"250 MKD command successful."];
            
        }
    }
    [sender sendMessage:cmdStr];
}

// ----------------------------------------------------------------------------------------------------------
-(void)doCdUp:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
    
    if (g_XMFTP_LogEnabled) {
        XMFTPLog(@"CurrentDir is %@",[self visibleCurrentDir]);
    }
    
    NSString *upDir=[[self visibleCurrentDir] stringByDeletingLastPathComponent];
    
    
    
    if ( [self changedCurrentDirectoryTo:upDir] )											// checks to see if its ok before moving
    {
        [sender sendMessage:@"250 CDUP command successful."];
    }
    else
    {
        // create message saying you cant go to that directory - FIXME
        [ sender sendMessage:@"550 CDUP command failed." ];									// CHECKME - look at a typical ftp mkdr command failure message
    }
}

// ----------------------------------------------------------------------------------------------------------
-(void)doRnfr:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
    self.rnfrFilename = [self makeFilePathFrom:[self fileNameFromArgs:arguments]];
    
    if ( [ self accessibleFilePath:self.rnfrFilename ] )										// FIXME - finish function
    {
        
        if ( [[ NSFileManager defaultManager] fileExistsAtPath: rnfrFilename ] )
        {
            [ sender sendMessage:@"350 RNFR command successful." ];
        }
        else
            [ sender sendMessage:@"550 RNFR command failed." ];
    }
}

// ----------------------------------------------------------------------------------------------------------
-(void)doRnto:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
    // FIXME - check its ok to use the new filename - ie in sandbox/basedir
    
    if ( self.rnfrFilename == nil ){
        [ sender sendMessage:@"550 RNTO command failed." ];
        return;
    }
    
    NSError *error;
    NSString *rntoFilename = [self makeFilePathFrom:[self fileNameFromArgs:arguments]];
    
    
    if (g_XMFTP_LogEnabled) {
        XMFTPLog(@"%@", rnfrFilename);
    }
    
    if ([self validNewFilePath:rntoFilename])													// FIXME - finish function
    {
        if ( [[ NSFileManager defaultManager] moveItemAtPath:rnfrFilename toPath: rntoFilename error:&error ] ){
            [ server didReceiveFileListChanged];
            [ sender sendMessage:@"250 RNTO command successful." ];
        }
        else{
            NSString *errorString = [error localizedDescription];
            if (g_XMFTP_LogEnabled) {
                XMFTPLog( @"RNTO failed %@", errorString );
            }
            [ sender sendMessage:@"550 RNTO command failed." ];
        }
        //		[rnfrFilename release];
        //		rnfrFilename = nil;
        //		[rntoFilename release];													// auto released, should be fine to just forget
        
    }
    else
    {
        [ sender sendMessage:@"550 RNTO command failed." ];										// CHECKME - have a look at a typical server response for this error
    }
}

/////////////////////////////////////////
#pragma mark UTILITIES

// ----------------------------------------------------------------------------------------------------------
-(NSString*)makeFilePathFrom:(NSString*)filename
// ----------------------------------------------------------------------------------------------------------
{
    if ( [filename characterAtIndex:0] == '/' )						// if absolute file position
    {
        if ( server.changeRoot )									// then its actually relative to basedir
        {
            return [ [server.baseDir stringByAppendingPathComponent: filename] stringByResolvingSymlinksInPath ];
        }
        else
        {
            return [filename copy];											// Don't want to hang onto a variable, user should retain if needed.
        }
        
    }
    else															// add onto the current file postion
    {
        return [ currentDir stringByAppendingPathComponent:filename];					// FIXME / CHECKME - need to set autorelease or retain etc.
        
    }
}

// ----------------------------------------------------------------------------------------------------------
-(unsigned long long)fileSize:(NSString*)filePath
// ----------------------------------------------------------------------------------------------------------
{
    
    
    NSError *error;
    NSDictionary *fileAttribs = [ [ NSFileManager defaultManager ] attributesOfItemAtPath:filePath error:&error ];
    
    NSNumber *fileSize = [ fileAttribs valueForKey:NSFileSize ];
    
    if (g_XMFTP_LogEnabled) {
        XMFTPLog(@"File size is %qu ", [fileSize unsignedLongLongValue]);
    }
    return [ fileSize unsignedLongLongValue];
    
}
// ----------------------------------------------------------------------------------------------------------
-(NSString*)fileNameFromArgs:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
    NSString *filename = [NSString string];
    
    if (g_XMFTP_LogEnabled) {
        XMFTPLog(@"%@", [arguments description ]);
    }
    
    if ([arguments count] >1) {
        
        
        for ( int n=1; n<[arguments count]; n++) {														// Start at arg2,
            // test to see if argument begins with a - as it might be just a parameter, not the name
            if (![[[arguments objectAtIndex:n] substringToIndex:1] isEqualToString:@"-"])
            {
                if ([filename length]==0) {
                    filename = [arguments objectAtIndex:n ];
                }
                else {
                    filename = [ NSString stringWithFormat:@"%@ %@", filename, [arguments objectAtIndex:n ] ];						// autoreleased
                }
                
                
            }
            else {
                
                if (g_XMFTP_LogEnabled) {
                    XMFTPLog(@"HYPHEN FOUND IGNORE");
                }
            }
            
            
        }
    }
    
    
    
    return  filename  ;			// autoreleased
}


// ----------------------------------------------------------------------------------------------------------
- (Boolean)changedCurrentDirectoryTo:(NSString *)newDirectory // NOTE - there is probably a unix way of expanding pathnames and taking all the ../. stuff out.   : should look this up
// ----------------------------------------------------------------------------------------------------------
{
    NSFileManager *fileManager = [ NSFileManager defaultManager ];
    
    NSString *currentDirectory = [ fileManager currentDirectoryPath ];  // store current directory
    NSString *testDirectory, *expandedPath;
    
    
    expandedPath = [self rootedPath:newDirectory ];
    
    // TEST IF ALLOWED TO USE THAT DIRECTORY ( ie is it in allowable filesystem )
    
    if (![self canChangeDirectoryTo:expandedPath]) {
        return false;
    }
    
    
    // CHANGE TO NEW DIRECTORY & GET SYSTEM FILE PATH ( no .. etc )
    if ( ! [ fileManager changeCurrentDirectoryPath:expandedPath ] )	// try changing to new directory
    {
        return false;													// Not a valid directory
    }
    
    testDirectory = [ fileManager currentDirectoryPath ];				// get new directory as string
    
    
    // CHANGE BACK
    if ( ! [ fileManager changeCurrentDirectoryPath:currentDirectory ] )// Change back to our own directory
    {
        return false;													// Not a valid directory, shouldnt happen, but could u know
    }
    
    
		  
    self.currentDir = [ testDirectory copy ];								// make a copy, and retain release
    
    return true;														// its fine.  would have failed before if not possible
}
// ----------------------------------------------------------------------------------------------------------
-(Boolean)canChangeDirectoryTo:(NSString *)testDirectory // NOTE - there is probably a unix way of expanding pathnames and taking all the ../. stuff out.   : should look this up 	
// ----------------------------------------------------------------------------------------------------------
{
    if (  [server changeRoot] && ( ! [ testDirectory hasPrefix:server.baseDir ]) ) {
        return false;
    }
    else {
        return true;
    }
    
    
}
// ----------------------------------------------------------------------------------------------------------
- (Boolean)accessibleFilePath:(NSString*)filePath						// checks its a proper name, and also in the chroot sandbox if set
// ----------------------------------------------------------------------------------------------------------
{
    // check, file is accessible within servers rights
    // && file exists
    
    // FIXME - need to expand the filename reference somehow to check where it really is, whether in sandbox. praps look  at tnftpd src code for this.
    
    return [ [ NSFileManager defaultManager ] fileExistsAtPath:filePath ];
}
// ----------------------------------------------------------------------------------------------------------
- (Boolean)validNewFilePath:(NSString*)filePath
// ----------------------------------------------------------------------------------------------------------
{	
    return true;			// FIXME - check this is within the area we can write to
}
// ----------------------------------------------------------------------------------------------------------
- (NSString *)visibleCurrentDir
// ----------------------------------------------------------------------------------------------------------
{
    
    if ( server.changeRoot )											// if root changed, to basedir
    {
        NSUInteger alength = [server.baseDir length ];							// length of basedir
        
        if (g_XMFTP_LogEnabled) {
            XMFTPLog(@"Length is %lu", alength );
        }

        NSString *aString = [ currentDir substringFromIndex:alength ];	// get the bit after basedir
        
        if ( ! [ aString hasSuffix:@"/" ] )								// add a / if needed
        {
            aString = [ aString stringByAppendingString:@"/" ];										
        }
        
        
        // return with basedir prefix removed.
        
        return aString;				// returns just the end part, not the base part
    }
    else
    {
        return currentDir;							// return complete path
    }
    
    
}
// ----------------------------------------------------------------------------------------------------------
-(NSString *)rootedPath:(NSString*)path
// ----------------------------------------------------------------------------------------------------------
{
    NSString  *expandedPath;
    
    // GET FULL PATH OF NEW DIRECTORY
    if ( [ path characterAtIndex:0 ] == '/')					// if its an absolute path
    {
        if ( server.changeRoot )										// if rooted, its a relative path really, so add to basedir
        {
            expandedPath = [ [server.baseDir stringByAppendingPathComponent: path] stringByResolvingSymlinksInPath ];
        }
        else															// it really is an absolute path
        {
            expandedPath = path;								// use the absolute path
        }
    }
    else																// or append onto currentdir ( the one the client chose ), as its a relative path
    {
        expandedPath =[[ currentDir  stringByAppendingPathComponent:path ] stringByResolvingSymlinksInPath];		
    }
    
    expandedPath = [ expandedPath stringByStandardizingPath ];
    
    return expandedPath; // Autoreleased etc
    
}


@end
